#include "cglm/cglm.h"
#include <GL/glew.h>
#include <SDL2/SDL.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>

#include "glUtils.h"
#include "initSDL.h"

#define TINYOBJ_LOADER_C_IMPLEMENTATION
#include "tinyobj_loader_c.h"

const uint32_t width = 640, height = 480;

void get_res_path(char *des, const char *name) {
    if (name[0] == '.') {
        name = name + 2;
    }
    sprintf(des, "res/%s", name);
    if (des[strlen(des) - 1] == '\n') {
        des[strlen(des) - 1] = '\0';
    }
}

char *get_file_data(size_t *len, const char *filename) {
    char path[1024];
    get_res_path(path, filename);
    FILE *obj_file = fopen(path, "r");
    if (!obj_file) {
        *len = 0;
        return NULL;
    }
    fseek(obj_file, 0, SEEK_END);
    size_t data_len = ftell(obj_file);
    fseek(obj_file, 0, SEEK_SET);
    char *data = (char *)malloc(data_len);
    memset(data, 0, data_len);
    fread(data, 1, data_len, obj_file);
    fclose(obj_file);
    if (len != NULL) {
        *len = data_len;
    }
    return data;
}

tinyobj_attrib_t attrib;
tinyobj_shape_t *shapes = NULL;
size_t num_shapes;
tinyobj_material_t *materials = NULL;
size_t num_materials;

int LoadObjAndConvert(const char *filename) {
    size_t file_len;

    const char *data = get_file_data(&file_len, filename);
    if (data == NULL) {
        puts("open obj file filed.");
        exit(-1);
    }
    printf("file size: %d\n", (int)file_len);
    {
        unsigned int flags = TINYOBJ_FLAG_TRIANGULATE;
        int ret = tinyobj_parse_obj(&attrib, &shapes, &num_shapes, &materials,
                                    &num_materials, data, file_len, flags,
                                    get_file_data);
        if (ret != TINYOBJ_SUCCESS) {
            exit(-1);
        }
        return 1;
    }
    exit(-1);
}

GLuint InitCubeMap(GLuint program, size_t texn, const char *uni) {
    GLint u_cube = glGetUniformLocation(program, uni);
    assert(u_cube >= 0);
    SDL_Surface *negx = SDL_LoadBMP("res/brige/negx.bmp");
    assert(negx != NULL);
    SDL_Surface *negy = SDL_LoadBMP("res/brige/negy.bmp");
    assert(negy != NULL);
    SDL_Surface *negz = SDL_LoadBMP("res/brige/negz.bmp");
    assert(negz != NULL);
    SDL_Surface *posx = SDL_LoadBMP("res/brige/posx.bmp");
    assert(posx != NULL);
    SDL_Surface *posy = SDL_LoadBMP("res/brige/posy.bmp");
    assert(posy != NULL);
    SDL_Surface *posz = SDL_LoadBMP("res/brige/posz.bmp");
    assert(posz != NULL);

    GLuint tex_cubemap;
    glGenTextures(1, &tex_cubemap);
    assert(tex_cubemap >= 0);
    glActiveTexture(GL_TEXTURE0 + texn);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex_cubemap);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, negx->w, negx->h, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, negx->pixels);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, negy->w, negy->h, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, negy->pixels);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, negz->w, negz->h, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, negz->pixels);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, posx->w, posx->h, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, posx->pixels);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, posy->w, posy->h, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, posy->pixels);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, posz->w, posz->h, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, posz->pixels);
    glUniform1i(u_cube, texn);

    SDL_FreeSurface(negx);
    SDL_FreeSurface(negy);
    SDL_FreeSurface(negz);
    SDL_FreeSurface(posx);
    SDL_FreeSurface(posy);
    SDL_FreeSurface(posz);
    return u_cube;
}

int main(int argc, char *argv[]) {
    int run = 1;
    SDL_Window *win = InitSDLWindow("cubemap", width, height);
    LoadObjAndConvert("monkey.obj");

    vec3 *vertices = (vec3 *)malloc(sizeof(vec3) * attrib.num_faces);
    vec3 *normals = (vec3 *)malloc(sizeof(vec3) * attrib.num_faces);
    GLushort *idx = (GLushort *)malloc(sizeof(GLushort) * attrib.num_faces);

    for (int i = 0; i < attrib.num_faces; i++) {
        tinyobj_vertex_index_t verts = attrib.faces[i];
        idx[i] = i;
        // idx[i] = verts.v_idx;
        // printf("#%d %d\n", idx[i], verts.v_idx);
        float *p = &attrib.vertices[verts.v_idx * 3];
        vertices[i][0] = p[0];
        vertices[i][1] = p[1];
        vertices[i][2] = p[2];
        p = &attrib.normals[verts.vn_idx * 3];
        normals[i][0] = p[0];
        normals[i][1] = p[1];
        normals[i][2] = p[2];
    }

    glEnable(GL_DEPTH_TEST);
    glClearColor(0.8, 0.8, 0.8, 1.0);

    axis_t axis = InitAxis();
    GLuint program = InitShader("cubemap");
    assert(program != 0);

    GLint a_pos = glGetAttribLocation(program, "a_Position");
    GLint a_norm = glGetAttribLocation(program, "a_Normal");

    GLuint b_verts =
        InitArrayBuffer((float *)vertices, sizeof(vec3) * attrib.num_faces);
    GLuint b_norm =
        InitArrayBuffer((float *)normals, sizeof(vec3) * attrib.num_faces);

    GLint u_cube = InitCubeMap(program, 0, "u_Cube");
    GLint u_vp = glGetUniformLocation(program, "u_VPMatrix");
    assert(u_vp >= 0);
    GLint u_model = glGetUniformLocation(program, "u_ModelMatrix");
    assert(u_model >= 0);
    GLint u_normal = glGetUniformLocation(program, "u_NormalMatrix");
    assert(u_normal >= 0);
    GLint u_eye = glGetUniformLocation(program, "u_eyePosition");
    assert(u_eye >= 0);

    GLuint idx_buf =
        InitElementsBuffer(idx, sizeof(GLushort) * attrib.num_faces);

    mat4 model = GLM_MAT4_IDENTITY_INIT, normal = GLM_MAT4_IDENTITY_INIT, v, p,
         vp;
    mat4 rotate = GLM_MAT4_IDENTITY_INIT;
    vec3 _axis_z = {0.0, 0.0, 1.0}, _axis_xy;
    vec4 eye = {1.5, 1.5, 0, 1.0}, center = {0.0, 0.0, 0.0},
         up = {0.0, 0.0, -1.0}, _eye;
    float angle_z = 0.0, angle_xy = 0.0;    
    vec4 __eye;

    glm_rotate(rotate, angle_z, _axis_z);
    glm_mat4_mulv(rotate, eye, __eye);
    glm_mat4_identity(rotate);
    glm_cross(_axis_z, __eye, _axis_xy);
    glm_rotate(rotate, -angle_xy, _axis_xy);
    glm_mat4_mulv(rotate, __eye, _eye);
    glm_lookat(_eye, center, up, v);

    glm_perspective_default((float)width / height, p);
    glm_mul(p, v, vp);

    glUseProgram(program);
    glUniformMatrix4fv(u_vp, 1, GL_FALSE, vp[0]);
    glUniformMatrix4fv(u_model, 1, GL_FALSE, model[0]);
    glUniformMatrix4fv(u_normal, 1, GL_TRUE, normal[0]);
    glUniform3fv(u_eye, 1, eye);

    
    while (run) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                run = 0;
            } else if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_RIGHT) {
                    angle_z += 0.05;
                } else if (event.key.keysym.sym == SDLK_LEFT) {
                    angle_z -= 0.05;
                } else if (event.key.keysym.sym == SDLK_UP) {
                    angle_xy += 0.05;
                    if (angle_xy > M_PI_2) {
                        angle_xy = M_PI_2 - 0.01;
                    }
                } else if (event.key.keysym.sym == SDLK_DOWN) {
                    angle_xy -= 0.05;
                    if (angle_xy < -M_PI_2) {
                        angle_xy = -M_PI_2 + 0.01;
                    }
                }
                glm_mat4_identity(rotate);
                glm_rotate(rotate, angle_z, _axis_z);
                glm_mat4_mulv(rotate, eye, __eye);
                glm_mat4_identity(rotate);
                glm_cross(_axis_z, __eye, _axis_xy);
                glm_rotate(rotate, -angle_xy, _axis_xy);
                glm_mat4_mulv(rotate, __eye, _eye);

                glm_lookat(_eye, center, up, v);
                glm_mul(p, v, vp);
            }
        }

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUseProgram(program);
        glUniformMatrix4fv(u_vp, 1, GL_FALSE, vp[0]);
        glUniform3fv(u_eye, 1, _eye);
        InitAttrBuffer(b_verts, a_pos, 3);
        InitAttrBuffer(b_norm, a_norm, 3);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, u_cube);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_buf);
        glDrawElements(GL_TRIANGLES, attrib.num_faces, GL_UNSIGNED_SHORT, 0);
        DrawAxis(&axis, vp[0]);
        SDL_GL_SwapWindow(win);
    }

    tinyobj_attrib_free(&attrib);
    tinyobj_shapes_free(shapes, num_shapes);
    tinyobj_materials_free(materials, num_materials);
    free(vertices);
    free(idx);
    SDL_DestroyWindow(win);
    return 0;
}